//
// Created by HP on 2022/1/31.
//

#include "tmc_model.h"
#include "tmc_utils.h"

// libs
#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader/tiny_obj_loader.h>
#include <glm/gtx/hash.hpp>
// std
#include <unordered_map>

namespace std {
    template <>
    struct hash<tmc::TmcModel::Vertex> {
        size_t operator() (tmc::TmcModel::Vertex const &vertex) const {
            size_t seed = 0;
            tmc::hashCombine(seed, vertex.position, vertex.color, vertex.normal, vertex.uv);
            return seed;
        }
    };
}
namespace tmc {

    TmcModel::TmcModel(const TmcModel::Builder &builder) {
        createVAO();
        recordVAO();
        createVertexBuffer(builder.vertices);
        createIndexBuffer(builder.indices);
        stopRecord();
    }

    TmcModel::~TmcModel() {
        glDeleteBuffers(1, &vertexBuffer);
        if (hasIndexBuffer) {
            glDeleteBuffers(1, &indexBuffer);
        }
        glDeleteVertexArrays(1, &VAO);
    }

    void TmcModel::createVertexBuffer(const std::vector<Vertex> &vertices) {
        vertexCount = static_cast<uint32_t>(vertices.size());
        assert(vertexCount >= 3 && "Vertex count must be at least 3.");

        glGenBuffers(1, &vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(vertices[0]), vertices.data(), GL_STATIC_DRAW);
        // 设置数组格式描述
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, uv));
        glEnableVertexAttribArray(3);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    void TmcModel::createIndexBuffer(const std::vector<GLuint> &indices) {
        indexCount = static_cast<uint32_t>(indices.size());
        hasIndexBuffer = indexCount > 0;
        if (!hasIndexBuffer) {
            return;
        }

        glGenBuffers(1, &indexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * sizeof(indices[0]), indices.data(), GL_STATIC_DRAW);
    }

    std::unique_ptr<TmcModel> TmcModel::createModelFromFile(const std::string &filepath) {
        Builder builder{};
        builder.loadModel(filepath);
        return std::make_unique<TmcModel>(builder);
    }

    void TmcModel::bind() const {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBindVertexArray(VAO);
    }

    void TmcModel::draw() const {
        glDrawElements(GL_TRIANGLES, static_cast<GLint>(indexCount), GL_UNSIGNED_INT, 0);
    }

    void TmcModel::Builder::loadModel(const std::string &filepath) {
        tinyobj::attrib_t attribs{}; // 储存 顶点坐标、颜色、法线、纹理坐标
        std::vector<tinyobj::shape_t> shapes{}; // 储存 index
        std::vector<tinyobj::material_t> materials{}; // 材质
        std::string warn, err;

        if (!tinyobj::LoadObj(&attribs, &shapes, &materials, &warn, &err, filepath.c_str())) {
            throw std::runtime_error(warn + err);
        }

        vertices.clear();
        indices.clear();

        std::unordered_map<Vertex, uint32_t> uniqueVertices{};
        for (const auto& shape: shapes) {
            for (const auto& index: shape.mesh.indices) {
                Vertex vertex{};
                // 因为 index 是可选的
                // 所以使用 -1 表示没有提供该数据
                if (index.vertex_index >= 0) {
                    vertex.position = {
                        attribs.vertices[3 * index.vertex_index + 0],
                        attribs.vertices[3 * index.vertex_index + 1],
                        attribs.vertices[3 * index.vertex_index + 2],
                    };

                    vertex.color = {
                        attribs.colors[3 * index.vertex_index + 0],
                        attribs.colors[3 * index.vertex_index + 1],
                        attribs.colors[3 * index.vertex_index + 2],
                    };
                }
                if (index.normal_index >= 0) {
                    vertex.normal = {
                        attribs.normals[3 * index.normal_index + 0],
                        attribs.normals[3 * index.normal_index + 1],
                        attribs.normals[3 * index.normal_index + 2],
                    };
                }
                if (index.texcoord_index >= 0) {
                    vertex.uv = {
                        attribs.texcoords[2 * index.texcoord_index + 0],
                        attribs.texcoords[2 * index.texcoord_index + 1],
                    };
                }

                if (uniqueVertices.count(vertex) == 0) {
                    uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
                    vertices.push_back(vertex);
                }
                indices.push_back(uniqueVertices[vertex]);
            }
        }
    }
}
